package plus.lgx.textsearcher.searcher.extender;

import plus.lgx.textsearcher.config.SearcherConfig;

/**
 * Class name: DefaultExtender
 *
 * Create time: 12/17/22 7:42 PM
 *
 * @author lgx
 * @version 1.0
 */
public class DefaultExtender extends Extender {
    public DefaultExtender(SearcherConfig config) {
        super(config);
    }

    /**
     * 最大的字符扩展数
     */
    private final int maxExtendCharsCount = config.maxExtendCharsCount;
    /**
     * 搜索的内容的长度
     */
    private final int len = config.searchContent.length();
    /**
     * 分隔符
     */
    private final char[][] DELIMITER = config.DELIMITER;

    private final int[] indices = new int[6];

    /**
     * <h2>从命中的子串的索引的位置向前后扩展得到一个句子。</h2>
     * <p>
     *     扩展的分隔符有三种，分别为
     *     <ol>
     *         <li>标点符号</li><li>左括号</li><li>右括号</li>
     *     </ol>
     *     默认的分隔符为
     *     <ol>
     *         <li>标点符号："　。！？.\n" 中的任一字符，其中的 "\n" 表示换行</li>
     *         <li>左括号："「" 中的任一字符</li>
     *         <li>右括号："」" 中的任一字符</li>
     *     </ol>
     *     分隔符也可以手动配置。
     * </p>
     * <p>
     *     分隔符中的左括号和右括号有对应关系，由配置的顺序决定。例如配置的左括号为 "({"，
     *     配置的右括号为 ")}"，则 '(' 与 ')' 对应，'{' 与 '}' 对应。
     *     允许配置相同的字符为左括号和右括号，例如配置左括号为 "（："，右括号为 "）："，这时字符 '：' 同时为左括号和右括号，而且是对应的。
     *     也允许左（右）括号没有其对应的右（左）括号。例如配置左括号为 "（：{"，右括号为 "）："，这时左括号 '{' 没有对应的右括号。
     * </p>
     * <p>
     *     对于检索时的不同情况，该方法会扩展得到不同的句子（子串）。下面分情况说明。
     *     请注意，下文将 "从<i>检索到的内容</i>的第一个字符在整个字符串的索引往前遇到的第一个分隔符" 称为前面，
     *     将 "从<i>检索到的内容</i>的最后一个字符在整个字符串的索引往后遇到的第一个分隔符" 称为后面。
     *     <ol>
     *         <li>
     *             当前面是<i>左括号</i>或<i>标点符号</i>，后面也是<i>标点符号</i>，而且都没有超出最大的扩展字符数时，
     *             则扩展得到<i>从前面往后一个字符到后面</i>的字符串。即不包含前面的标点符号，包含后面的标点符号。
     *             例如：对于以下字符串 "对于不同的情况，该方法会扩展得到不同的句子（子串）。下面分情况说明。"
     *             使用默认分隔符，检索内容 "说明"，在该字符串的索引为 31，31 往前遇到的第一个分隔符是 '。'，
     *             索引为 25，检索内容的最后一个字符在整个字符串中的索引为 32，32 往后遇到的第一个分隔符为 '。'，索引为 33。
     *             因此最终扩展得到的串为索引从 26 （包含）到 33 （包含）的串，即 "下面分情况说明。"。
     *         </li>
     *         <li>
     *             当前面是<i>左括号</i>，后面是对应的<i>右括号</i>，而且都没有超出最大的扩展字符数时，
     *             则扩展得到<i>从前面到后面</i>的字符串。即包含前面和后面的括号。
     *             例如：对于以下字符串 "对于不同的情况：「该方法会扩展得到不同的句子」。下面分情况说明。"
     *             使用默认分隔符，检索内容 "扩展"，在该字符串的索引为 13，13 往前遇到的第一个分隔符是 '「'，
     *             索引为 8，检索内容的最后一个字符在整个字符串中的索引为 14，14 往后遇到的第一个分隔符为 '」'，索引为 22。
     *             因此最终扩展得到的串为索引从 8 （包含）到 22 （包含）的串，即 "「该方法会扩展得到不同的句子」"。
     *         </li>
     *         <li>
     *             若前面是<i>右括号</i>，后面是<i>标点符号</i>，则会往前继续寻找分隔符直到前面的括号都闭合，
     *             再往前遇到<i>标点符号</i>或<i>左括号</i>，若没有超出最大的扩展字符数，则此时按情况 1 处理。<br>
     *             例如：对于以下字符串 "对于不同的情况：「该方法会扩展得到不同的句子」下面分情况说明。"
     *             使用默认分隔符，检索内容 "说明"，最终扩展得到整个字符串。
     *         </li>
     *         <li>
     *             若前面是<i>标点符号</i>，后面是<i>左括号</i>，则会往后继续寻找分隔符直到后面的括号都闭合，
     *             再往后遇到<i>标点符号</i>，若没有超出最大的扩展字符数，则此时按情况 1 处理。<br>
     *             例如：对于以下字符串 "对于不同的情况：「该方法会扩展得到不同的句子」下面分情况说明。"
     *             使用默认分隔符，检索内容 "不同的情况"，最终扩展会得到整个字符串。
     *         </li>
     *         <li>
     *             无论何时，在往前或往后寻找分隔符时，若遇到文件开头或结尾（即直到文件的开头或结尾都没有找到分隔符），
     *             或直到最大的扩展字符数（即到超出最大的扩展字符数都没有找到分隔符），则停止寻找，返回从停止查找的位置截取的子串。
     *         </li>
     *     </ol>
     * </p>
     *
     * @return 扩展得到的串
     */
    @Override
    public String extend() {
        int beginInd = Math.max(index.get() - 1, 0);
        int endInd   = Math.min(index.get() + (m == null ? len : m.end() - m.start()), off - 1);

        boolean a = findPreDelimiter(beginInd),
                b = findPostDelimiter(endInd);

        extend0(a, b);

        index.set(Math.min(indices[3], off - 1));
        return content.substring(indices[0], Math.min(indices[3], off)).trim();
    }

    private void extend0(boolean a, boolean b) {
        if (!a && b) {
            if (indices[4] == 1) {
                _11();
                return;
            }
            indices[3]++;
            return;
        }
        if (a && !b) {
            if (indices[1] == 2) {
                _2n();
                return;
            }
            indices[3]++;
            return;
        }
        if (!a) {
            indices[3]++;
            return;
        }

        if ((indices[1] == 0 || indices[1] == 1) && indices[4] == 0) {_n0(); return;}
        if (indices[1] == 0 && indices[4] == 1) {_01(); return;}
        if (indices[1] == 0 && indices[4] == 2) {_02(); return;}
        if (indices[1] == 1 && indices[4] == 1) {_11(); return;}
        if (indices[1] == 1 && indices[4] == 2) {_12(); return;}
        if (indices[1] == 2) _2n();
    }

    private void _2n() {
        int i1 = indices[2], brackets = 1;
        while (indices[0] != 0 && findPreDelimiter(indices[0] - 1)) {
            if (i1 == indices[2]) {
                if (indices[1] == 1 && --brackets == 0) {
                    continue;
                }
                if (indices[1] == 2) {
                    brackets++;
                }
                if (brackets == -1) {
                    switch (indices[4]) {
                        case 0 -> _n0();
                        case 1 -> _11();
                        case 2 -> _12();
                    }
                    return;
                }
            }
            if (brackets == 0 && indices[1] == 0) {
                switch (indices[4]) {
                    case 0 -> _n0();
                    case 1 -> _01();
                    case 2 -> _02();
                }
                return;
            }
        }
        indices[3]++;
    }

    private void _n0() {
        indices[0]++;
        indices[3]++;
    }

    private void _11() {
        int i1 = indices[5], brackets = 2;
        while (true) {
            if (indices[3] == off - 1 || !findPostDelimiter(indices[3] + 1)) {
                indices[3]++;
                return;
            }
            if (i1 == indices[5]) {
                if (indices[4] == 2) {
                    if (--brackets == 0) {
                        indices[3]++;
                        return;
                    }
                }
                if (indices[4] == 1) {
                    brackets++;
                }
            }
        }
    }

    private void _02() {
        int i1 = indices[2], brackets = 1;
        while (true) {
            if (indices[0] == 0 || !findPreDelimiter(indices[0] - 1)) {
                indices[3]++;
                return;
            }
            if (i1 == indices[2]) {
                if (indices[1] == 1) {
                    if (--brackets == 0) {
                        indices[3]++;
                        return;
                    }
                }
                if (indices[1] == 2) {
                    brackets++;
                }
            }
        }
    }

    private void _01() {
        int i1 = indices[5], brackets = 1;
        do {
            if (indices[3] == off - 1 || !findPostDelimiter(indices[3] + 1)) {
                indices[3]++;
                return;
            }
            if (i1 == indices[5]) {
                if (indices[4] == 2) brackets--;
                if (indices[4] == 1) brackets++;
            }
            if (brackets == 0 && indices[4] == 0) {
                indices[3]++;
                return;
            }
        } while (brackets != 0 || indices[4] != 2);
        if (indices[3] == off - 1 || !findPostDelimiter(indices[3] + 1)) {
            indices[3]++;
            return;
        }
        switch (indices[4]) {
            case 0 -> _n0();
            case 1 -> _01();
            case 2 -> _02();
        }
    }

    private void _12() {
        int brackets = 1;
        if (indices[2] == indices[5]) indices[3]++;
        else while (true) {
            if (indices[3] == off - 1 || !findPostDelimiter(indices[3] + 1)) {
                indices[3]++;
                return;
            }
            if (indices[2] == indices[5]) {
                if (indices[4] == 2) {
                    if (--brackets == 0) {
                        indices[3]++;
                        return;
                    }
                }
                if (indices[4] == 1) {
                    brackets++;
                }
            }
        }
    }

    private boolean findPreDelimiter(int startInd) {
        for(char b; startInd > -1 && index.get() - startInd < maxExtendCharsCount; --startInd) {
            b = content.charAt(startInd);
            for (int i = 0, delimiterLength = DELIMITER.length; i < delimiterLength; i++)
                for (int j = 0, charsLength = DELIMITER[i].length; j < charsLength; j++)
                    if (b == DELIMITER[i][j]) {
                        indices[0] = startInd;
                        indices[1] = i;
                        indices[2] = j;
                        return true;
                    }
        }
        indices[0] = Math.max(0, startInd);
        return false;
    }

    private boolean findPostDelimiter(int endInd) {
        for(char e;
            endInd < off && endInd - index.get() - (m == null ? len : m.end() - m.start()) < maxExtendCharsCount;
            ++endInd) {
            e = content.charAt(endInd);
            for (int i = DELIMITER.length - 1; i > -1; i--)
                for (int j = 0, charsLength = DELIMITER[i].length; j < charsLength; j++)
                    if (e == DELIMITER[i][j]) {
                        indices[3] = endInd;
                        indices[4] = i;
                        indices[5] = j;
                        return true;
                    }
        }
        indices[3] = Math.min(off - 1, endInd);
        return false;
    }

}
